package cn.coder.jdbc.core;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.coder.jdbc.annotation.Column;
import cn.coder.jdbc.support.JSql;
import cn.coder.jdbc.util.FieldUtils;

public final class EntityWrapper {
	private static final Logger logger = LoggerFactory.getLogger(EntityWrapper.class);

	private JSql jSql;
	private HashMap<String, Object> values;
	private Object entity;
	private SQLType sqlType;

	private final EntityMapping mapping;

	public EntityWrapper(EntityMapping mapping) {
		this.mapping = mapping;
		this.values = new HashMap<>();
	}

	public void build(Object data, SQLType type) {
		long start = System.nanoTime();
		this.entity = data;
		this.sqlType = type;
		fillColumns();
		switch (type) {
		case SELECT:
			buildSelect();
			break;
		case INSERT:
			buildInsert();
			break;
		case UPDATE:
			buildUpdate();
			break;
		case DELETE:
			buildDelete();
			break;
		default:
			throw new IllegalArgumentException("Un support type of '" + sqlType.name() + "'");
		}
		if (logger.isDebugEnabled())
			logger.debug("Build '{}' sql in {} ns", sqlType.name(), System.nanoTime() - start);
	}

	private void fillColumns() {
		Column col;
		Set<Field> fields = FieldUtils.getDeclaredFields(this.entity.getClass());
		for (Field field : fields) {
			col = field.getAnnotation(Column.class);
			if (col != null && this.mapping.findColumn(col.value())) {
				try {
					if (!field.isAccessible())
						field.setAccessible(true);
					values.put(col.value(), field.get(this.entity));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					logger.error("字段赋值失败", e);
				}
			}
		}
	}

	private void buildSelect() {
		try {
			ArrayList<Object> args = new ArrayList<>();
			StringBuffer sql1 = new StringBuffer();
			Object obj = null;
			Set<String> keys = values.keySet();
			for (String key : keys) {
				obj = values.get(key);
				if (obj != null) {
					sql1.append("`" + key + "`=? AND ");
					args.add(obj);
				}
			}
			if (sql1.length() > 0)
				sql1.delete(sql1.length() - 4, sql1.length());
			String sql = "SELECT COUNT(1) FROM `" + this.mapping.getTable() + "` WHERE " + sql1.toString();

			this.jSql = JSql.create(sql, toArray(args));
		} catch (Exception e) {
			logger.error("Create select sql faild", e);
		}
	}

	private void buildInsert() {
		try {
			ArrayList<Object> args = new ArrayList<>();
			StringBuffer sql1 = new StringBuffer("(");
			StringBuffer sql2 = new StringBuffer("(");
			Object obj = null;
			Set<String> keys = values.keySet();
			for (String key : keys) {
				obj = values.get(key);
				if (obj != null) {
					sql1.append("`" + key + "`,");
					sql2.append("?,");
					args.add(obj);
				}
			}
			sql1.deleteCharAt(sql1.length() - 1);
			sql1.append(")");
			sql2.deleteCharAt(sql2.length() - 1);
			sql2.append(")");
			String sql = "INSERT INTO `" + this.mapping.getTable() + "`" + sql1.toString() + " VALUES "
					+ sql2.toString();

			this.jSql = JSql.create(sql, toArray(args));
		} catch (Exception e) {
			logger.error("Create insert sql faild", e);
		}
	}

	private void buildUpdate() {
		try {
			ArrayList<Object> args = new ArrayList<>();
			StringBuffer sql1 = new StringBuffer(" SET ");
			Object obj = null;
			Set<String> keys = values.keySet();
			for (String key : keys) {
				obj = values.get(key);
				if (obj != null && !this.mapping.findPrimaryKey(key)) {
					sql1.append("`" + key + "`=?,");
					args.add(obj);
				}
			}
			sql1.deleteCharAt(sql1.length() - 1);
			StringBuffer sql2 = new StringBuffer();
			final String[] primaryKeys = this.mapping.getPrimaryKeys();
			if (primaryKeys.length > 0) {
				for (String key : primaryKeys) {
					sql2.append("`" + key + "`=? AND ");
					args.add(values.get(key));
				}
			}
			if (sql2.length() > 0)
				sql2.delete(sql2.length() - 4, sql2.length());
			String sql = "UPDATE `" + this.mapping.getTable() + "`" + sql1.toString() + " WHERE " + sql2.toString();

			this.jSql = JSql.create(sql, toArray(args));
		} catch (Exception e) {
			logger.error("Create update sql faild", e);
		}
	}

	private void buildDelete() {
		try {
			ArrayList<Object> args = new ArrayList<>();
			StringBuffer sql1 = new StringBuffer();
			Object obj = null;
			Set<String> keys = values.keySet();
			for (String key : keys) {
				obj = values.get(key);
				if (obj != null) {
					sql1.append("`" + key + "`=? AND ");
					args.add(obj);
				}
			}
			if (sql1.length() > 0)
				sql1.delete(sql1.length() - 4, sql1.length());
			String sql = "DELETE FROM `" + this.mapping.getTable() + "` WHERE " + sql1.toString();

			this.jSql = JSql.create(sql, toArray(args));
		} catch (Exception e) {
			logger.error("Create delete sql faild", e);
		}
	}

	private static Object[] toArray(ArrayList<Object> args) {
		Object[] data = new Object[args.size()];
		return args.toArray(data);
	}

	public void setGeneratedKey(Object value) throws SQLException {
		final Field generateKeyField = this.mapping.getGenerateKeyField();
		if (generateKeyField != null) {
			FieldUtils.setValue(generateKeyField, this.entity, value);
		}
	}

	public JSql getJSql() {
		return this.jSql;
	}

	public SQLType getSqlType() {
		return this.sqlType;
	}

	public boolean returnGeneratedKey() {
		return this.sqlType == SQLType.INSERT && this.mapping.getGenerateKeyField() != null;
	}

	public void clear() {
		values.clear();
		this.jSql = null;
		this.entity = null;
		this.sqlType = null;
	}

	public enum SQLType {
		SELECT, INSERT, UPDATE, DELETE
	}
}